    /* 
 * File:   PhysicsLogic.cpp
 * Author: RedEyedKiller
 * 
 * Created on 22 Σεπτέμβριος 2010, 7:09 μμ
 */

#include "PhysicsLogic.h"
#include "physics/CollisionEvents.h"
#include "physics/EntityContactInfo.h"
#include "physics/PhysicalObject.h"
#include "physics/PhysicsManager.h"
#include "physics/TileContactInfo.h"
#include "sdl/Application.h"
#include "Entity.h"
#include "Logger.h"
#include "ScriptLogic.h"
#include "Utilities.h"
#include "ServiceLocator.h"
#include "State.h"
#include "StateStack.h"

namespace EntitySystem
{

/*
struct Equality : public unary_function<physicsSystem::PhysicsInternals::EntityContactInfo*,bool>
{
    bool operator()(physicsSystem::PhysicsInternals::EntityContactInfo* ptr)
    {
        return ptr->GetCollidedWith() == against;
    }
    Entity* against;
};
 */

PhysicsLogic::PhysicsLogic() : material(0.2, 0)
{
    registeredTo = NULL;
    invertedMass = 1;
    damping = 0.5;
    ghost = false;
    entEontactIterator = entContactList.begin( );
    awake = true;
    canSleep = true;
    
}

PhysicsLogic::PhysicsLogic(const PhysicsLogic& org) : PhysicalObject(org), rect(org.rect), material(org.material)
{
    dampingOnX = org.dampingOnX;
    dampingOnY = org.dampingOnY;
    ignoreGeneralForces = org.ignoreGeneralForces;
    entEontactIterator = entContactList.begin( );
    canSleep = org.canSleep;
    awake = true;
    //make sure this will not fall to sleep imidiatly
    motion = physicsSystem::SLEEP_THREASHOLD * 2;

    registeredTo = NULL;
}

PhysicsLogic::~PhysicsLogic()
{
    if( registeredTo != NULL )
    {
        registeredTo->RemovePhysicsLogic( this );
    }

    ResetContactList( );
}

PhysicsLogic* PhysicsLogic::Clone()
{
    return new PhysicsLogic( *this );
}

void PhysicsLogic::Update(float seconds)
{
    //make sure that the body is awake before updating it.
    //    if (!awake)
    //        return;

    oldPosition = parent->GetPosition( );
    //calculate new position
    parent->SetPosition( parent->GetPosition( ) + velocity * seconds );
    //calculate moment's acceleration
    Math::Vector2F acceleration( generalAcceleration );
    if( ignoreGeneralForces ) acceleration.Set( Math::Vector2F::Zero );
    ////by adding general acceleration with Σf/mass
    acceleration += forces * invertedMass;
    oldVelocity = velocity;
    //calculate new velocity
    velocity += acceleration*seconds;
    if( dampingOnX )
    {
        velocity.SetX( velocity.GetX( ) * damping );
    }
    if( dampingOnY )
    {
        velocity.SetY( velocity.GetY( ) * damping );
    }

    ResetForces( );

    if( canSleep )
    {
        float currentMotion = velocity.DotProduct( velocity );
        float bias = 0.3; //std::pow(0.5,seconds);
        motion = bias * motion + ( 1 - bias ) * currentMotion;

        if( motion < physicsSystem::SLEEP_THREASHOLD )
            awake = false;
        else if( motion > physicsSystem::SLEEP_THREASHOLD * 10 )
            motion = physicsSystem::SLEEP_THREASHOLD * 10;
    }

}

void PhysicsLogic::OnEntityCollision(Entity* collided, const Math::Vector2F& contactNormal)
{
    ScriptPredicates::ScriptExecutorOnEntityC exec;
    exec.cNormal = contactNormal;
    exec.entity = collided;
    GetParent( )->ExecuteOnScripts(exec);
}

void PhysicsLogic::OnTileCollision(std::string& tile, Math::Vector2F& contactNormal)
{
    ScriptPredicates::ScriptExecutorOnTileC exec;
    exec.cNormal = contactNormal;
    exec.tile = &tile;
    GetParent( )->ExecuteOnScripts(exec);
}

void PhysicsLogic::AdjustPositionPerImass(const Math::Vector2F& unitsPerImass)
{
    parent->SetPosition( GetPosition( ) + unitsPerImass * invertedMass );
}

void PhysicsLogic::AdjustVelocityPerImass(const Math::Vector2F& unitsPerImass)
{
    velocity = ( velocity + unitsPerImass * invertedMass );
}

bool PhysicsLogic::HasSolidContactOn(const Math::Vector2F& direction)
{
    return(HasEntityContactOn( direction ) || HasTileContactOn( direction, "Solid" ) );
}

bool PhysicsLogic::HasEntityContactOn(const Math::Vector2F& direction)
{
    EntityInfoList::iterator it = entContactList.begin( );
    EntityInfoList::const_iterator end = entContactList.end( );

    Math::Vector2F invertedDirection( direction );
    invertedDirection.Invert( );

    bool res = false;
    for(; it != end && !res; ++it )
    {
        res = res || ( ( *it )->GetContactNormal( ) == invertedDirection );
    }
    return res;
}

bool PhysicsLogic::HasTileContactOn(const Math::Vector2F& direction,const std::string& flag)
{
    TileInfoList::iterator it = tileContactList.begin( );
    TileInfoList::const_iterator end = tileContactList.end( );

    Math::Vector2F invertedDirection( direction );
    invertedDirection.Invert( );
    
    unsigned long flagId  = ServiceLocator<StateStack>::GetService()->Top()->GetPhysicsManager()->GetTileProperties()->ValueOf(flag);
    bool res = false;
    for(; it != end && !res; ++it )
    {
        res = res || ( ( *it )->contactNormal == invertedDirection && ( *it )->flag & flagId );
    }
    return res;
}

bool PhysicsLogic::HasTileContactOn(const Math::Vector2F& direction, int flag)
{
    TileInfoList::iterator it = tileContactList.begin( );
    TileInfoList::const_iterator end = tileContactList.end( );

    Math::Vector2F invertedDirection( direction );
    invertedDirection.Invert( );

    bool res = false;
    for(; it != end && !res; ++it )
    {
        res = res || ( ( *it )->contactNormal == invertedDirection && ( *it )->flag & flag );
    }
    return res;
}

Math::Vector2F PhysicsLogic::GetPosition() const
{
    return parent->GetPosition( );
}

Math::IntersectionType PhysicsLogic::CollisionCheck(PhysicsLogic* pl, Math::Vector2F& axisInfo)
{
    return rect.Intersect( pl->rect, axisInfo );
}

Math::Rect* PhysicsLogic::GetRect()
{
    return &rect;
}

void PhysicsLogic::SetRect(const Math::Rect& rect)
{
    this->rect = rect;
}

void PhysicsLogic::AdjustPosition(float x, float y)
{
    parent->AdjustPosition( x, y );
}

const Math::Vector2F PhysicsLogic::GetOldPosition() const
{
    return oldPosition;
}

const Math::Vector2F PhysicsLogic::GetOldVelocity() const
{
    return oldVelocity;
}

void PhysicsLogic::SetMaterial(const physicsSystem::PhysicsMaterial& material)
{
    this->material = material;
}

physicsSystem::PhysicsMaterial PhysicsLogic::GetMaterial() const
{
    return material;
}

void PhysicsLogic::SetIgnoreGeneralForces(bool value)
{
    this->ignoreGeneralForces = value;
}

bool PhysicsLogic::IsIgnoreGeneralForces() const
{
    return ignoreGeneralForces;
}

void PhysicsLogic::ApplyDampingOnX(bool dampingOnX)
{
    this->dampingOnX = dampingOnX;
}

bool PhysicsLogic::IsDampingOnX() const
{
    return dampingOnX;
}

void PhysicsLogic::ApplyDampingOnY(bool dampingOnY)
{
    this->dampingOnY = dampingOnY;
}

bool PhysicsLogic::IsDampingOnY() const
{
    return dampingOnY;
}

/**
 * Clears contact list.
 */
void PhysicsLogic::ResetContactList()
{
    SafeRelease<physicsSystem::EntityContactInfo*>( entContactList );
    SafeRelease<physicsSystem::TileContactInfo*>( tileContactList );
    //invalidate iterator.
    entEontactIterator = entContactList.begin( );
}

/**
 * Add a physics logic object int the contect list
 */
void PhysicsLogic::AddEntityContact(physicsSystem::EntityContactInfo* toAdd)
{
    entContactList.push_back( toAdd );
    //invalidate iterator.(not needed)
    entEontactIterator = entContactList.begin( );
}

/**
 * Add a physics logic object int the contect list
 */
void PhysicsLogic::AddTileContact(physicsSystem::PhysicsInternals::TileContactInfo* toAdd)
{
    tileContactList.push_back( toAdd );
}

/**
 * 
 * @return the next contact struct or null if none else exists.
 */
physicsSystem::EntityContactInfo* PhysicsLogic::GetNextContact()
{
    //rewinding at the end of the iteration and increasing the iterator in each call 
    //is used to achive that chain calls of this method actually iterate through 
    //all the list only one time.

    //if at the end return NULL to indicate that we reached our limit.
    if( entEontactIterator == entContactList.end( ) )
    {
        //rewind iterator for future use.
        entEontactIterator = entContactList.begin( );
        return NULL;
    }
    //retrieve data to be returned
    physicsSystem::EntityContactInfo* toReturn = *entEontactIterator;
    //increase iterator for future use.
    ++entEontactIterator;
    return toReturn;
}

/**
 * Removes the contact with the given entity (if any)
 */
void PhysicsLogic::DeleteContact(EntitySystem::Entity* toKill)
{
    std::list<physicsSystem::PhysicsInternals::EntityContactInfo*>::iterator i = entContactList.begin( );
    std::list<physicsSystem::PhysicsInternals::EntityContactInfo*>::iterator end = entContactList.end( );

    for(; i != end; )
    {
        if( ( *i )->GetCollidedWith( ) == toKill )
        {
            delete (*i );
            i = entContactList.erase( i );
        }
        else
        {
            ++i;
        }
    }

    entEontactIterator = entContactList.begin( );
}


/**
 * Sets this logic's physics manager.
 *
 * @param manager
 */

void PhysicsLogic::SetPhysicsManager(physicsSystem::PhysicsManager *manager)
{
    if( registeredTo != NULL )
    {
        registeredTo->RemovePhysicsLogic( this );
    }

    registeredTo = manager;
}


/**
 * Adds the result of a force into ΣF (forces) of this object.
 * This method is created to be used only by scrips.
 * @forceToAdd
 */
void PhysicsLogic::scAddForce(const Math::Vector2F& forceToAdd)
{
    forces += forceToAdd;
}

/**
 * Returns the velocity of this physical object.
 * This method is created to be used only by scrips.
 * @return 
 */
Math::Vector2F PhysicsLogic::scGetVelocity()
{
    return velocity;
}

/**
 * Sets the velocity of this.
 * @param velocity
 * This method is created to be used only by scrips.
 */
void PhysicsLogic::scSetVelocity(const Math::Vector2F& velocity)
{
    this->velocity = velocity;
}


void PhysicsLogic::SetGroupIndex(int groupIndex)
{
    this->groupIndex = groupIndex;
}

int PhysicsLogic::GetGroupIndex() const
{
    return groupIndex;
}

void PhysicsLogic::Awake()
{
    this->awake = true;
    motion = 2 * physicsSystem::SLEEP_THREASHOLD;
}

bool PhysicsLogic::IsAwake() const
{
    return awake;
}

void PhysicsLogic::SetCanSleep(bool canSleep)
{
    this->canSleep = canSleep;
}

float PhysicsLogic::GetMotion() const
{
    return motion;
}

}
